package handler

import (
	"fmt"
	"html/template"
	"net/http"
	"strings"
	"time"

	"jiance_server/config"
	"jiance_server/handler/base"
	"jiance_server/log"
	"jiance_server/model"
	"jiance_server/mongo"
	"jiance_server/util"

	"github.com/cdongyang/library/utils"
	. "github.com/cdongyang/library/web/handler/base"
	"github.com/cdongyang/library/web/webutils"
	mgo "gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

var classPath = "/class/"
var noticePath = "/notice/"

func init() {
	http.Handle("/login", base.NewHandlerWithMethods(login, http.MethodPost))
	http.Handle("/logout", base.NewHandlerWithMethods(logout, http.MethodPost))
	http.Handle("/alterPwd", base.NewHandlerWithMethods(alterPwd, http.MethodPost))
	http.Handle("/list", base.NewHandlerWithMethods(list, http.MethodPost))
	http.Handle("/query", base.NewHandlerWithMethods(query, http.MethodPost))
	http.Handle("/query/myclass", base.NewHandlerWithMethods(queryMyClass, http.MethodPost))
	http.Handle("/subscribe", base.NewHandlerWithMethods(subscribe, http.MethodPost))
	http.Handle(classPath, base.NewHandlerWithMethods(class, http.MethodGet))
	http.Handle(noticePath, base.NewHandlerWithMethods(notice, http.MethodGet))
	// test handler
	http.Handle("/add/notice", base.NewHandlerWithMethods(addNotice, http.MethodPost))
	http.Handle("/register", base.NewHandlerWithMethods(register, http.MethodPost))
	go watcher()
}

type loginReq struct { // also use by test
	Account  string `json:"account"`
	Password string `json:"password"`
	Sign     string `json:"sign"`
}

func login(w http.ResponseWriter, r *http.Request) {
	req := loginReq{}
	err := webutils.GetReqJSON(r, &req)
	if err != nil {
		Fail(w, err.Error())
		return
	}
	if req.Sign != utils.MD5(config.GetConfig().Salt, req.Password) {
		Fail(w, "请求失败!")
		return
	}
	if len(req.Account) != 18 {
		Fail(w, "帐号应为18位身份证号!")
		return
	}
	if len(req.Password) < 6 || len(req.Password) > 20 {
		Fail(w, "密码应为6-20位!")
		return
	}
	user, err := model.FindUserByID(req.Account, bson.M{"_id": 1, "Token": 1, "Nickname": 1, "Password": 1})
	if err != nil {
		Fail(w, err.Error())
		return
	}
	if user.Password != util.EncodePwd(req.Password) {
		Fail(w, "密码错误!")
		return
	}
	err = user.SetToken()
	if err != nil {
		Fail(w, err.Error())
		return
	}
	Success(w, user)
}

type logoutReq struct {
	Sign string `json:"sign"`
}

func logout(w http.ResponseWriter, r *http.Request) {
	req := logoutReq{}
	err := webutils.GetReqJSON(r, &req)
	if err != nil {
		Fail(w, err.Error())
		return
	}
	err = model.DeleteToken(req.Sign, true)
	if err != nil {
		Fail(w, err.Error())
		return
	}
	Success(w, struct{}{})
}

type alterPwdReq struct {
	NewPwd string `json:"newPwd"`
	OldPwd string `json:"oldPwd"`
	Sign   string `json:"sign"`
}

func alterPwd(w http.ResponseWriter, r *http.Request) {
	req := alterPwdReq{}
	err := webutils.GetReqJSON(r, &req)
	if err != nil {
		Fail(w, err.Error())
		return
	}
	user, err := model.FindUserByToken(req.Sign, true, bson.M{"_id": 1, "Password": 1})
	if err != nil {
		Fail(w, err.Error())
		return
	}
	if user.Password != util.EncodePwd(req.OldPwd) {
		Fail(w, "旧密码错误!")
		return
	}
	user.Password = util.EncodePwd(req.NewPwd)
	err = user.SavePassword()
	if err != nil {
		Fail(w, err.Error())
		return
	}
	Success(w, struct{}{})
}

type listReq struct {
	Sign      string `json:"sign"`
	Type      string `json:"type"`
	Token     string `json:"token"`
	PageIndex int    `json:"pageIndex"`
	PageSize  int    `json:"pageSize"`
}

func list(w http.ResponseWriter, r *http.Request) {
	req := listReq{}
	err := webutils.GetReqJSON(r, &req)
	if err != nil {
		Fail(w, err.Error())
		return
	}
	if req.Sign != utils.MD5(config.GetConfig().Salt) {
		Fail(w, "请求失败!")
		return
	}
	if req.PageIndex < 0 {
		Fail(w, "页码有误!")
		return
	}
	if req.PageSize <= 0 || req.PageSize > 100 {
		Fail(w, "页大小有误")
		return
	}
	switch req.Type {
	case "notice":
		notices, err := model.ListNotice(req.PageIndex, req.PageSize, bson.M{"Title": 1, "_id": 1, "CreateTime": 1, "Link": 1})
		if err != nil {
			Fail(w, err.Error())
		}
		Success(w, notices)
	case "class":
		classes, err := model.ListClass(req.PageIndex, req.PageSize, bson.M{"Title": 1, "_id": 1, "CreateTime": 1})
		if err != nil {
			Fail(w, err.Error())
			return
		}
		for i := range classes {
			classes[i].SetLink(classPath)
		}
		Success(w, classes)
	default:
		Fail(w, "type有误:"+req.Type)
	}
}

type queryReq struct {
	Sign string `json:"sign"`
	Type string `json:"type"`
}

func query(w http.ResponseWriter, r *http.Request) {
	req := queryReq{}
	err := webutils.GetReqJSON(r, &req)
	if err != nil {
		Fail(w, err.Error())
		return
	}
	selector := bson.M{}
	switch req.Type {
	case "grade":
		selector["Grade"] = 1
	case "fee":
		selector["Fee"] = 1
	case "schedule":
		selector["Schedule"] = 1
	default:
		Fail(w, "type字段有误!")
		return
	}
	user, err := model.FindUserByToken(req.Sign, true, selector)
	if err != nil {
		Fail(w, err.Error())
		return
	}
	switch req.Type {
	case "grade":
		Success(w, user.Grade)
	case "fee":
		Success(w, user.Fee)
	case "schedule":
		Success(w, user.Schedule)
	}
}

type queryMyClassReq struct {
	Sign      string `json:"sign"`
	PageIndex int    `json:"pageIndex"`
	PageSize  int    `json:"pageSize"`
}

func queryMyClass(w http.ResponseWriter, r *http.Request) {
	req := queryMyClassReq{}
	err := webutils.GetReqJSON(r, &req)
	if err != nil {
		Fail(w, err.Error())
		return
	}
	if req.PageIndex < 0 {
		Fail(w, "页码有误!")
		return
	}
	if req.PageSize <= 0 || req.PageSize > 100 {
		Fail(w, "页大小有误")
		return
	}
	user, err := model.FindUserByToken(req.Sign, true, bson.M{"ClassIDs": 1})
	if err != nil {
		Fail(w, err.Error())
		return
	}
	classes, err := model.FindClassByIDs(user.ClassIDs, req.PageIndex, req.PageSize, bson.M{"Title": 1, "_id": 1, "CreateTime": 1})
	if err != nil {
		Fail(w, err.Error())
		return
	}
	for i := range classes {
		classes[i].SetLink(classPath)
	}
	Success(w, classes)
}

type subscribeReq struct {
	ID   string `json:"id"`
	Sign string `json:"sign"`
}

func subscribe(w http.ResponseWriter, r *http.Request) {
	req := subscribeReq{}
	err := webutils.GetReqJSON(r, &req)
	if err != nil {
		Fail(w, err.Error())
		return
	}
	user, err := model.FindUserByToken(req.Sign, true, bson.M{"_id": 1, "ClassIDs": 1})
	if err != nil {
		Fail(w, err.Error())
		return
	}
	var inslice = false
	for _, val := range user.ClassIDs {
		if val == req.ID {
			inslice = true
			break
		}
	}
	if inslice {
		Fail(w, "您已经预约过了!")
		return
	}
	class, err := model.FindClassByID(req.ID, bson.M{"_id": 1, "Num": 1, "Max": 1})
	if err != nil {
		Fail(w, err.Error())
		return
	}
	if class.Num >= class.Max {
		Fail(w, "课程已经被预约满了!")
		return
	}
	err = class.SubscribedBy(user.ID)
	if err != nil {
		Fail(w, err.Error())
		return
	}
	Success(w, struct{}{})
}

func class(w http.ResponseWriter, r *http.Request) {
	id := strings.TrimPrefix(r.URL.Path, classPath)
	class, err := model.FindClassByID(id, nil)
	if err != nil {
		fmt.Fprint(w, err.Error())
		return
	}
	tpl, err := template.ParseFiles(config.GetConfig().Path + "/template/class.html")
	if err != nil {
		fmt.Fprint(w, err.Error())
		return
	}
	ctime := time.Unix(class.CreateTime, 0)
	err = tpl.Execute(w, struct {
		*model.Class
		Year    int
		Month   int
		Day     int
		Residue int64
	}{
		Class:   class,
		Year:    ctime.Year(),
		Month:   int(ctime.Month()),
		Day:     ctime.Day(),
		Residue: class.Max - class.Num,
	})
	if err != nil {
		log.Errorln(err)
	}
}

func notice(w http.ResponseWriter, r *http.Request) {
	id := strings.TrimPrefix(r.URL.Path, noticePath)
	notice, err := model.FindNoticeByID(id, nil)
	if err != nil {
		fmt.Fprint(w, err.Error())
		return
	}
	w.Write([]byte(notice.Content))
}

func register(w http.ResponseWriter, r *http.Request) {
	req := model.User{}
	err := webutils.GetReqJSON(r, &req)
	if err != nil {
		Fail(w, err.Error())
		return
	}
	for _, name := range classNames {
		req.Fee = append(req.Fee, model.Fee{Type: name, Status: "未缴费"})
		req.Schedule = append(req.Schedule, model.Schedule{Type: name, Status: "未完成"})
	}
	req.Password = util.EncodePwd(req.Password)
	err = mongo.Users.WithC(func(c *mgo.Collection) error {
		return c.Insert(req)
	})
	if err != nil {
		Fail(w, err.Error())
		return
	}
	Success(w, struct{}{})
}

func addNotice(w http.ResponseWriter, r *http.Request) {
	req := model.Notice{}
	err := webutils.GetReqJSON(r, &req)
	if err != nil {
		Fail(w, err.Error())
		return
	}
	if req.Title == "" {
		Fail(w, "title为空")
		return
	}
	req.ID = utils.MD5(time.Now().Format("2006-01-02 15:04:05"), req.Title)
	if req.Link == "" {
		req.Link = config.GetConfig().Host + noticePath + req.ID
	}
	err = mongo.Notice.WithC(func(c *mgo.Collection) error {
		return c.Insert(req)
	})
	if err != nil {
		Fail(w, err.Error())
		return
	}
	Success(w, struct{}{})
}

var classNames = map[string]string{"1": "NE-上机", "2": "SE-路由", "3": "SE-园区", "4": "SE-广域", "5": "TE-笔试", "6": "TE-上机"}
var classTeacher = map[string]string{"1": "赵治东", "2": "张东亮", "3": "彭天付", "4": "田海荣", "5": "张荣", "6": "李渊"}

func watcher() {
	var createClass = func() {
		date := time.Now().Format("2006-01-02")
		err := mongo.Class.WithC(func(c *mgo.Collection) error {
			bulk := c.Bulk()
			inserts := make([]interface{}, 0, len(classNames))
			for k, v := range classNames {
				inserts = append(inserts, model.Class{
					ID:         date + "-" + k,
					Title:      v + " - " + date,
					CreateTime: time.Now().Unix(),
					Max:        20,
					Type:       v,
					Teacher:    classTeacher[k],
				})
			}
			bulk.Insert(inserts...)
			res, err := bulk.Run()
			log.Debugln(res)
			return err
		})
		if err != nil {
			log.Errorln(err)
		}
	}
	createClass()
	time.AfterFunc(time.Now().Truncate(time.Hour*24).Add(time.Hour*24).Sub(time.Now()), func() {
		for range time.Tick(time.Hour * 24) {
			createClass()
		}
	})
}
